import { execute, select } from '@evershop/postgres-query-builder';
import { v4 as uuidv4 } from 'uuid';
import { buildUrl } from '../../../../../lib/router/buildUrl.js';
import { buildFilterFromUrl } from '../../../../../lib/util/buildFilterFromUrl.js';
import { camelCase } from '../../../../../lib/util/camelCase.js';
import { toPrice } from '../../../../checkout/services/toPrice.js';
import { CategoryCollection } from '../../../services/CategoryCollection.js';
import { getCategoriesBaseQuery } from '../../../services/getCategoriesBaseQuery.js';
import { getFilterableAttributes } from '../../../services/getFilterableAttributes.js';
import { getProductsByCategoryBaseQuery } from '../../../services/getProductsByCategoryBaseQuery.js';
import { ProductCollection } from '../../../services/ProductCollection.js';

export default {
  Query: {
    category: async (_, { id }, { pool }) => {
      const query = getCategoriesBaseQuery();
      query.where('category.category_id', '=', id);
      const result = await query.load(pool);
      return result ? camelCase(result) : null;
    },
    currentCategory: async (_, args, { currentUrl, currentRoute, pool }) => {
      if (currentRoute?.id !== 'categoryView') {
        return null;
      }
      const { params } = currentRoute;
      if (!params || !params.uuid) {
        return null;
      }
      const query = getCategoriesBaseQuery();
      query.where('uuid', '=', params.uuid);
      const filtersFromUrl = buildFilterFromUrl(currentUrl);
      const result = await query.load(pool);
      return result
        ? {
            ...camelCase(result),
            products: async (_, { filters = [] }) => {
              const query = await getProductsByCategoryBaseQuery(
                result.category_id,
                true
              );
              const root = new ProductCollection(query);
              // Can we merge 2 filters here and the filters take higher priority. Each is an array [{key, operation, value}]
              const mergedFilters = [...filtersFromUrl, ...filters];
              await root.init(mergedFilters, false);
              return root;
            }
          }
        : null;
    },
    categories: async (_, { filters = [] }, { user }) => {
      const query = getCategoriesBaseQuery();
      const root = new CategoryCollection(query);
      await root.init(filters, !!user);
      return root;
    }
  },
  Category: {
    products: async (category, { filters = [] }, { user }) => {
      // This is a hack for mycategory
      if (typeof category.products === 'function') {
        return await category.products(category, { filters });
      }
      const query = await getProductsByCategoryBaseQuery(
        category.categoryId,
        !user
      );
      const root = new ProductCollection(query);
      await root.init(filters, !!user);
      return root;
    },
    availableAttributes: async (category) => {
      const results = await getFilterableAttributes(category.categoryId);
      return results;
    },
    priceRange: async (category, _, { pool }) => {
      const query = await getProductsByCategoryBaseQuery(
        category.categoryId,
        true
      );
      query
        .select('MIN(product.price)', 'min')
        .select('MAX(product.price)', 'max');
      const result = await query.load(pool);
      return {
        min: result.min || 0,
        minText: toPrice(result.min || 0, true),
        max: result.max || 0,
        maxText: toPrice(result.max || 0, true)
      };
    },
    url: async (category, _, { pool }) => {
      // Get the url rewrite for this category
      const urlRewrite = await select()
        .from('url_rewrite')
        .where('entity_uuid', '=', category.uuid)
        .and('entity_type', '=', 'category')
        .load(pool);
      if (!urlRewrite) {
        return buildUrl('categoryView', { uuid: category.uuid });
      } else {
        return urlRewrite.request_path;
      }
    },
    image: (category) => {
      const { image, name } = category;
      if (!image) {
        return null;
      } else {
        return {
          alt: name,
          url: image
        };
      }
    },
    hasChildren: async (category, _, { pool }) => {
      const query = select().from('category');
      query.where('category.parent_id', '=', category.categoryId);
      const results = await query.execute(pool);
      return results.length > 0;
    },
    children: async (category, _, { pool }) => {
      const query = select().from('category');
      query
        .leftJoin('category_description', 'des')
        .on(
          'des.category_description_category_id',
          '=',
          'category.category_id'
        );
      query.where('category.parent_id', '=', category.categoryId);
      const results = await query.execute(pool);
      return results.map((row) => camelCase(row));
    },
    path: async (category, _, { pool }) => {
      const query = await execute(
        pool,
        `WITH RECURSIVE category_path AS (
          SELECT category_id, parent_id, 1 AS level
          FROM category
          WHERE category_id = ${category.categoryId}
          UNION ALL
          SELECT c.category_id, c.parent_id, cp.level + 1
          FROM category c
          INNER JOIN category_path cp ON cp.parent_id = c.category_id
        )
        SELECT category_id FROM category_path ORDER BY level DESC`
      );
      const categories = query.rows;
      // Loop the categories and load the category description
      return Promise.all(
        categories.map(async (c) => {
          const query = select().from('category');
          query
            .leftJoin('category_description', 'des')
            .on(
              'des.category_description_category_id',
              '=',
              'category.category_id'
            );
          query.where('category.category_id', '=', c.category_id);
          return camelCase(await query.load(pool));
        })
      );
    },
    parent: async (category, _, { pool }) => {
      if (!category.parentId) {
        return null;
      }
      const query = select().from('category');
      query
        .leftJoin('category_description', 'des')
        .on(
          'des.category_description_category_id',
          '=',
          'category.category_id'
        );
      query.where('category.category_id', '=', category.parentId);
      return camelCase(await query.load(pool));
    },
    description: ({ description }) => {
      try {
        return JSON.parse(description);
      } catch (e) {
        // This is for backward compatibility. If the description is not a JSON string then it is a raw HTML block
        const rowId = `r__${uuidv4()}`;
        return [
          {
            size: 1,
            id: rowId,
            columns: [
              {
                id: 'c__c5d90067-c786-4324-8e24-8e30520ac3d7',
                size: 1,
                data: {
                  time: 1723347125344,
                  blocks: [
                    {
                      id: 'AU89ItzUa7',
                      type: 'raw',
                      data: {
                        html: description
                      }
                    }
                  ],
                  version: '2.30.2'
                }
              }
            ]
          }
        ];
      }
    }
  },
  Product: {
    category: async (product, _, { pool }) => {
      if (!product.categoryId) {
        return null;
      } else {
        const categoryQuery = getCategoriesBaseQuery();
        categoryQuery.where('category_id', '=', product.categoryId);
        const category = await categoryQuery.load(pool);
        return camelCase(category);
      }
    }
  }
};
